package com.navinfo.tripanalysis.common.arithmetic;

import com.navinfo.tripanalysis.common.arithmetic.annotation.Arithmetic;
import com.navinfo.tripanalysis.common.arithmetic.common.*;
import com.navinfo.tripanalysis.common.arithmetic.custom.VarDrivingEco;
import com.navinfo.tripanalysis.common.arithmetic.convert.EventDataConvert;
import com.navinfo.tripanalysis.common.arithmetic.exception.RangeAssert;
import com.navinfo.tripanalysis.common.arithmetic.manager.AbstractArithmetic;
import com.navinfo.tripanalysis.common.config.ConfigMap;
import com.navinfo.tripanalysis.common.arithmetic.common.VehicleInfo;
import com.navinfo.tripanalysis.common.protocol.java.EventDataPb;
import com.navinfo.tripanalysis.common.util.ArithmeticUtils;

import java.util.Optional;

/**
 * ECO经济驾驶算法<br/>
 * 依赖{@link Mileage}里程油耗算法<br/>
 * 依赖{@link DrivingSlope}计算的坡度数据<br/>
 * 赋值{@link CommonData#ecoDriving}当前点是否为ECO经济驾驶状态<br/>
 * 输出：统计、事件<br/>
 * @author 沈东生
 */
@Arithmetic(name = "ECO经济驾驶算法")
public class DrivingEco extends AbstractArithmetic<VarDrivingEco> {
    /**
     * 事件类型
     */
    private static final EventDataPb.EventType EVENT_TYPE = EventDataPb.EventType.ecoDriving;
    /**
     * ECO转速最小最大阀值，必须在坡度算法SlopeDriving之后初始化
     */
    private EcoRotationThreshold rotationThreshold;
    /**
     * ECO时长阀值 单位:MS
     */
    private int timeThreshold = ConfigMap.getInteger("E_T_Time_Threshold");
    /**
     * 判断当前是否为ECO经济驾驶算法
     */
    boolean isCurEcoDriving() {
        //初始化转速阀值
        if(null == rotationThreshold){
            rotationThreshold = getRotationThreshold();
        }
        CommonData data = getCommonData();
        // 车速>0 and发动机转速>=最小阈值 and 发动机转速<=最大阈值
        return data.getSpeedKmh()>0 && data.getRotation()>=rotationThreshold.getLow() && data.getRotation()<=rotationThreshold.getHigh();
//        return (data.getSpeedKmh()>0 && data.getRotation()>=ConfigMap.getDouble("E_T_Low_Threshold") && data.getRotation()<=ConfigMap.getDouble("E_T_High_Threshold"));
    }

    @Override
    public void calculate() {
        logger.debug("进入ECO经济驾驶算法.[terminalId:{}]", terminalId);

        VarDrivingEco var = getObj();
        CommonData commonData = getCommonData();

        //转速有效性校验
        boolean validRange = RangeAssert.ecoDrivingRotationIsInTheRange(commonData.getRotation());

        if (!validRange) {
            logger.info("引擎转速已超有效范围,当前点不统计。value is {}", commonData.getRotation());
        } else {
            boolean preEcoDriving = var.isPreEcoDriving();
            boolean curEcoDriving = isCurEcoDriving();
            logger.debug("ECO经济驾驶算法，ECO经济驾驶算法状态：preEcoDriving:{},curEcoDriving:{}.[terminalId:{}]", preEcoDriving, curEcoDriving, terminalId);

            //将ECO经济驾驶状态保存到公共参数，其它算法(引擎转速区间统计算法)依赖此参数
            commonData.setEcoDriving(curEcoDriving);

            //设置上一个点状态、供下个点计算
            var.setPreEcoDriving(curEcoDriving);

            //算法计算
            switch (getEventStatus(preEcoDriving, curEcoDriving)) {
                case START:
                    logger.debug("ECO经济驾驶算法，事件开始.[terminalId:{}]", terminalId);
                    startEventData(EVENT_TYPE);
                    break;
                case IN:
                    logger.debug("ECO经济驾驶算法，事件中.[terminalId:{}]", terminalId);
                    calcEventData(EVENT_TYPE);
                    break;
                case END:
                    logger.debug("ECO经济驾驶算法，事件结束.[terminalId:{}]", terminalId);
                    calcEventData(EVENT_TYPE);
                    endEvent();
                    break;
                default:
                    logger.debug("ECO经济驾驶算法，ECO经济驾驶算法状态：上一次和本次都未ECO经济驾驶算法，跳出算法.[terminalId:{}]", terminalId);
            }
        }

        //行程结束时要清理对应的未结束事件
        if (commonData.isTripLastNode()) {
            endEvent();
        }

        logger.debug("ECO经济驾驶算法结束.[terminalId:{}]", terminalId);
    }

    @Override
    public void collect(OuterStatisticData statisticDataOut) {
        VarDrivingEco var = getObj();
        statisticDataOut.setEcoDrivingMileage(ArithmeticUtils.mileageMToM(var.getMileage()));
        statisticDataOut.setEcoDrivingFuel(ArithmeticUtils.fuelMlToMl(var.getFuel()));
        statisticDataOut.setEcoDrivingDuration(ArithmeticUtils.timeMsToSecond(var.getDuration()));
    }

    /**
     * 处理事件的结束状态
     */
    private void endEvent() {
        Optional.ofNullable(drivingData.getEventData(EVENT_TYPE)).ifPresent(eventData -> {
            EventDataConvert.convertEnd(eventData, drivingData);
            if (Optional.ofNullable(eventData.getDuration()).orElse(0L) >= timeThreshold) {
                eventDataToVar(eventData);
                drivingData.addEventDataToList(eventData);
            } else {
                logger.debug("ECO经济驾驶算法时间{}ms,小于阈值{}ms，不进行统计", Optional.ofNullable(eventData.getDuration()).orElse(0L), timeThreshold);
            }

            //删除当前缓存
            drivingData.removeEventData(EVENT_TYPE);
        });
    }

    /**
     * 事件变量的累积里程油耗时长赋值到统计变量中
     * @param eventData
     */
    private void eventDataToVar(OuterEventData eventData){
        VarDrivingEco var = getObj();
        var.addMileage(Optional.ofNullable(eventData.getExtendField05()).orElse(0.0));
        var.addFuel(Optional.ofNullable(eventData.getExtendField06()).orElse(0.0));
        var.addDuration(Optional.ofNullable(eventData.getDuration()).orElse(0L).intValue());
    }

    /**
     * 获取ECO经济驾驶的转速上下阀值
     * @return
     */
    public EcoRotationThreshold getRotationThreshold() {
        EcoRotationThreshold threshold = new EcoRotationThreshold();

        //从车型配置信息获取阀值
        VehicleInfo vehicleInfo = drivingData.getVehicleInfo();
        if(null != vehicleInfo){
            threshold.setLow(vehicleInfo.getEngineECORotationLow());
            threshold.setHigh(vehicleInfo.getEngineECORotationHigh());
        }

        //如果根据车型没有获取阀值，则从配置文件中读取默认的阀值
        if (threshold.getLow() <= 0) {
            threshold.setLow(ConfigMap.getDouble("E_T_Low_Threshold"));
        }
        if (threshold.getHigh() <= 0) {
            threshold.setHigh(ConfigMap.getDouble("E_T_High_Threshold"));
        }

        // 上坡坡度每增加0.5°，经济转数的范围增加100转；
        // 下坡不变；累计增加300后不再继续增加；
        // 如： 平路1000~1200，坡度在1.2°时， 范围在1200~1400之间；
        double slope = drivingData.getCommonData().getSlope();
        if (slope > 0) {
            // ECO 坡度增加次数最大阀值
            int slopeMax = ConfigMap.getInteger("E_T_Slope_Max_Threshold");
            // ECO 坡度增加间隔坡度阀值 单位：度
            double slopeRange = ConfigMap.getDouble("E_T_Slope_Range_Threshold");
            // ECO 坡度增加转速阀值 单位：RPM
            double slopeRotation = ConfigMap.getDouble("E_T_Slope_Rotation_Threshold");

            int range = Math.min((int) (slope / slopeRange), slopeMax);
            if (range > 0) {
                threshold.low += range * slopeRotation;
                threshold.high += range * slopeRotation;
            }
            logger.debug("Eco slope={} range={} [terminalId:{}]",slope,range, terminalId);
        }

        logger.debug("EcoThreshold({},{}) [terminalId:{}]", threshold.low, threshold.high, terminalId);
        return threshold;
    }

    /**
     * Eco经济价值的转速阀值
     */
    public static class EcoRotationThreshold {
        private double low;
        private double high;
        public double getLow() {
            return low;
        }
        public void setLow(double low) {
            this.low = low;
        }
        public double getHigh() {
            return high;
        }
        public void setHigh(double high) {
            this.high = high;
        }
    }
}
